/**
 * @file
 * @brief RTD：XWDS：SOC
 * @author
 * + 隐星魂 (Roy Sun) <xwos@xwos.tech>
 * @copyright
 * + Copyright © 2015 xwos.tech, All Rights Reserved.
 * > Licensed under the Apache License, Version 2.0 (the "License");
 * > you may not use this file except in compliance with the License.
 * > You may obtain a copy of the License at
 * >
 * >         http://www.apache.org/licenses/LICENSE-2.0
 * >
 * > Unless required by applicable law or agreed to in writing, software
 * > distributed under the License is distributed on an "AS IS" BASIS,
 * > WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * > See the License for the specific language governing permissions and
 * > limitations under the License.
 */

#include "../board/std.h"
#include <xwos/lib/xwbop.h>
#include <xwos/osal/lock/spinlock.h>
#include <xwcd/ds/soc/chip.h>
#include <xwcd/ds/soc/gpio.h>
#include "Mcu.h"
#include "OsIf.h"
#include "Mcl.h"
#include "Port.h"
#include "Platform.h"
#include "Uart.h"
#include "CDD_I2c.h"
#include "Spi.h"
#include "Siul2_Dio_Ip.h"

#define MCALXWDS_GPIO_PORT_NUM 5U

struct mcalxwds_soc_drv_data {
        struct {
                struct xwos_splk lock;
        } gpio;
};

struct mcalxwds_soc_cfg {
        struct {
                Siul2_Dio_Ip_GpioType * regmap[MCALXWDS_GPIO_PORT_NUM][2];
        } gpio;
};

static
xwer_t mcalxwds_soc_drv_start(struct xwds_device * dev);

static
xwer_t mcalxwds_soc_drv_stop(struct xwds_device * dev);

#if defined(XWCDCFG_ds_PM) && (1 == XWCDCFG_ds_PM)
static
xwer_t mcalxwds_soc_drv_suspend(struct xwds_device * dev);

static
xwer_t mcalxwds_soc_drv_resume(struct xwds_device * dev);
#endif

static
xwer_t mcalxwds_soc_drv_gpio_req(struct xwds_soc * soc,
                                 xwid_t port, xwsq_t pinmask);

static
xwer_t mcalxwds_soc_drv_gpio_rls(struct xwds_soc * soc,
                                 xwid_t port, xwsq_t pinmask);

static
xwer_t mcalxwds_soc_drv_gpio_set(struct xwds_soc * soc,
                                 xwid_t port, xwsq_t pinmask);

static
xwer_t mcalxwds_soc_drv_gpio_reset(struct xwds_soc * soc,
                                   xwid_t port, xwsq_t pinmask);

static
xwer_t mcalxwds_soc_drv_gpio_toggle(struct xwds_soc * soc,
                                    xwid_t port, xwsq_t pinmask);

static
xwer_t mcalxwds_soc_drv_gpio_output(struct xwds_soc * soc,
                                    xwid_t port, xwsq_t pinmask,
                                    xwsq_t out);

static
xwer_t mcalxwds_soc_drv_gpio_input(struct xwds_soc * soc,
                                   xwid_t port, xwsq_t pinmask,
                                   xwsq_t * in);

const struct xwds_soc_driver mcalxwds_soc_drv = {
        .base = {
                .name = "mcalxwds.soc",
                .probe = NULL,
                .remove = NULL,
                .start = mcalxwds_soc_drv_start,
                .stop = mcalxwds_soc_drv_stop,
#if defined(XWCDCFG_ds_PM) && (1 == XWCDCFG_ds_PM)
                .suspend = mcalxwds_soc_drv_suspend,
                .resume = mcalxwds_soc_drv_resume,
#endif
        },

        .gpio_req = mcalxwds_soc_drv_gpio_req,
        .gpio_rls = mcalxwds_soc_drv_gpio_rls,
        .gpio_cfg = NULL,
        .gpio_set = mcalxwds_soc_drv_gpio_set,
        .gpio_reset = mcalxwds_soc_drv_gpio_reset,
        .gpio_toggle = mcalxwds_soc_drv_gpio_toggle,
        .gpio_output = mcalxwds_soc_drv_gpio_output,
        .gpio_input = mcalxwds_soc_drv_gpio_input,
};

const struct mcalxwds_soc_cfg mcalxwds_soc_cfg = {
        .gpio = {
                .regmap = {
                        [XWDS_GPIO_PORT_A] = {
                                PTA_L_HALF,
                                PTA_H_HALF,
                        },
                        [XWDS_GPIO_PORT_B] = {
                                PTB_L_HALF,
                                PTB_H_HALF,
                        },
                        [XWDS_GPIO_PORT_C] = {
                                PTC_L_HALF,
                                PTC_H_HALF,
                        },
                        [XWDS_GPIO_PORT_D] = {
                                PTD_L_HALF,
                                PTD_H_HALF,
                        },
                        [XWDS_GPIO_PORT_E] = {
                                PTE_L_HALF,
                                PTE_H_HALF,
                        },
                },
        },
};

atomic_xwsq_t mcalxwds_gpio_pin_state[] = {
        [XWDS_GPIO_PORT_A] = 0,
        [XWDS_GPIO_PORT_B] = 0,
        [XWDS_GPIO_PORT_C] = 0,
        [XWDS_GPIO_PORT_D] = 0,
        [XWDS_GPIO_PORT_E] = 0,
};

struct mcalxwds_soc_drv_data mcalxwds_soc_drv_data;

struct xwds_soc mcalxwds_soc = {
        /* attributes */
        .dev = {
                .name = "mcalxwds.soc",
                .id = 0,
                .resources = NULL,
                .drv = xwds_cast(struct xwds_driver *, &mcalxwds_soc_drv),
                .data = &mcalxwds_soc_drv_data,
        },
        .xwccfg = (void *)&mcalxwds_soc_cfg,
        .gpio = {
                .cfg = NULL,
                .pins = mcalxwds_gpio_pin_state,
                .port_num = xw_array_size(mcalxwds_gpio_pin_state),
                .pin_num = 32,
        },
};

static
xwer_t mcalxwds_soc_drv_start(struct xwds_device * dev)
{
        struct mcalxwds_soc_drv_data * drvdata;

        drvdata = dev->data;
        xwos_splk_init(&drvdata->gpio.lock);

#if (MCU_PRECOMPILE_SUPPORT == STD_ON)
        Mcu_Init(NULL_PTR);
#elif (MCU_PRECOMPILE_SUPPORT == STD_OFF)
        Mcu_Init(&Mcu_Config_BOARD_InitPeripherals);
#endif
        Mcu_InitClock(McuClockSettingConfig_0);
#if (MCU_NO_PLL == STD_OFF)
        while (MCU_PLL_LOCKED != Mcu_GetPllStatus()) {
                /* Busy wait until the System PLL is locked */
        }
        Mcu_DistributePllClock();
#endif
        OsIf_Init(NULL_PTR);
        Mcu_SetMode(McuModeSettingConf_Run);
#if (MCL_PRECOMPILE_SUPPORT == STD_ON)
        Mcl_Init(NULL_PTR);
#elif (MCL_PRECOMPILE_SUPPORT == STD_OFF)
        Mcl_Init(&Mcl_Config_BOARD_INITPERIPHERALS);
#endif
#if (STD_ON == PORT_PRECOMPILE_SUPPORT)
        Port_Init(NULL_PTR);
#elif (STD_OFF == PORT_PRECOMPILE_SUPPORT)
        Port_Init(&Port_Config);
#endif
        Platform_Init(NULL_PTR);
        Uart_Init(NULL_PTR);
        I2c_Init(&I2c_Config_BOARD_InitPeripherals);
        Spi_Init(NULL);
        Spi_SetAsyncMode(SPI_INTERRUPT_MODE);

        return XWOK;
}

static
xwer_t mcalxwds_soc_drv_stop(struct xwds_device * dev)
{
        XWOS_UNUSED(dev);
        Uart_Deinit();
        return XWOK;
}

#if defined(XWCDCFG_ds_PM) && (1 == XWCDCFG_ds_PM)
static
xwer_t mcalxwds_soc_drv_suspend(struct xwds_device * dev)
{
        XWOS_UNUSED(dev);
        return XWOK;
}

static
xwer_t mcalxwds_soc_drv_resume(struct xwds_device * dev)
{
        XWOS_UNUSED(dev);
        return XWOK;
}
#endif

static
xwer_t mcalxwds_soc_drv_gpio_req(struct xwds_soc * soc,
                                 xwid_t port, xwsq_t pinmask)
{
        XWOS_UNUSED(soc);
        XWOS_UNUSED(port);
        XWOS_UNUSED(pinmask);
        return XWOK;
}

static
xwer_t mcalxwds_soc_drv_gpio_rls(struct xwds_soc * soc,
                                 xwid_t port, xwsq_t pinmask)
{
        XWOS_UNUSED(soc);
        XWOS_UNUSED(port);
        XWOS_UNUSED(pinmask);
        return XWOK;
}

static
xwer_t mcalxwds_soc_drv_gpio_set(struct xwds_soc * soc,
                                 xwid_t port, xwsq_t pinmask)
{
        const struct mcalxwds_soc_cfg * soccfg;
        struct mcalxwds_soc_drv_data * drvdata;
        Siul2_Dio_Ip_GpioType * const * gpio;
        Siul2_Dio_Ip_PinsChannelType pinmskh;
        Siul2_Dio_Ip_PinsChannelType pinmskl;
        xwreg_t cpuirq;
        xwer_t rc;

        if (port < MCALXWDS_GPIO_PORT_NUM) {
                drvdata = soc->dev.data;
                soccfg = soc->xwccfg;
                gpio = soccfg->gpio.regmap[port];
                pinmskl = ((Siul2_Dio_Ip_PinsChannelType)pinmask &
                           (Siul2_Dio_Ip_PinsChannelType)0xFFFF);
                pinmskh = ((Siul2_Dio_Ip_PinsChannelType)(pinmask >> (xwsq_t)16) &
                           (Siul2_Dio_Ip_PinsChannelType)0xFFFF);
                xwos_splk_lock_cpuirqsv(&drvdata->gpio.lock, &cpuirq);
                Siul2_Dio_Ip_SetPins(gpio[0], pinmskl);
                Siul2_Dio_Ip_SetPins(gpio[1], pinmskh);
                xwos_splk_unlock_cpuirqrs(&drvdata->gpio.lock, cpuirq);
                rc = XWOK;
        } else {
                rc = -EINVAL;
        }
        return rc;
}

static
xwer_t mcalxwds_soc_drv_gpio_reset(struct xwds_soc * soc,
                                   xwid_t port, xwsq_t pinmask)
{
        const struct mcalxwds_soc_cfg * soccfg;
        struct mcalxwds_soc_drv_data * drvdata;
        Siul2_Dio_Ip_GpioType * const * gpio;
        Siul2_Dio_Ip_PinsChannelType pinmskh;
        Siul2_Dio_Ip_PinsChannelType pinmskl;
        xwreg_t cpuirq;
        xwer_t rc;

        if (port < MCALXWDS_GPIO_PORT_NUM) {
                drvdata = soc->dev.data;
                soccfg = soc->xwccfg;
                gpio = soccfg->gpio.regmap[port];
                pinmskl = ((Siul2_Dio_Ip_PinsChannelType)pinmask &
                           (Siul2_Dio_Ip_PinsChannelType)0xFFFF);
                pinmskh = ((Siul2_Dio_Ip_PinsChannelType)(pinmask >> (xwsq_t)16) &
                           (Siul2_Dio_Ip_PinsChannelType)0xFFFF);
                xwos_splk_lock_cpuirqsv(&drvdata->gpio.lock, &cpuirq);
                Siul2_Dio_Ip_ClearPins(gpio[0], pinmskl);
                Siul2_Dio_Ip_ClearPins(gpio[1], pinmskh);
                xwos_splk_unlock_cpuirqrs(&drvdata->gpio.lock, cpuirq);
                rc = XWOK;
        } else {
                rc = -EINVAL;
        }
        return rc;
}

static
xwer_t mcalxwds_soc_drv_gpio_toggle(struct xwds_soc * soc,
                                    xwid_t port, xwsq_t pinmask)
{
        const struct mcalxwds_soc_cfg * soccfg;
        struct mcalxwds_soc_drv_data * drvdata;
        Siul2_Dio_Ip_GpioType * const * gpio;
        Siul2_Dio_Ip_PinsChannelType pinmskh;
        Siul2_Dio_Ip_PinsChannelType pinmskl;
        xwreg_t cpuirq;
        xwer_t rc;

        if (port < MCALXWDS_GPIO_PORT_NUM) {
                drvdata = soc->dev.data;
                soccfg = soc->xwccfg;
                gpio = soccfg->gpio.regmap[port];
                pinmskl = ((Siul2_Dio_Ip_PinsChannelType)pinmask &
                           (Siul2_Dio_Ip_PinsChannelType)0xFFFF);
                pinmskh = ((Siul2_Dio_Ip_PinsChannelType)(pinmask >> (xwsq_t)16) &
                           (Siul2_Dio_Ip_PinsChannelType)0xFFFF);
                xwos_splk_lock_cpuirqsv(&drvdata->gpio.lock, &cpuirq);
                Siul2_Dio_Ip_TogglePins(gpio[0], pinmskl);
                Siul2_Dio_Ip_TogglePins(gpio[1], pinmskh);
                xwos_splk_unlock_cpuirqrs(&drvdata->gpio.lock, cpuirq);
                rc = XWOK;
        } else {
                rc = -EINVAL;
        }
        return rc;
}

static
xwer_t mcalxwds_soc_drv_gpio_output(struct xwds_soc * soc,
                                    xwid_t port, xwsq_t pinmask,
                                    xwsq_t out)
{
        struct mcalxwds_soc_drv_data * drvdata;
        Siul2_Dio_Ip_PinsChannelType pinmskh;
        Siul2_Dio_Ip_PinsChannelType pinmskl;
        Siul2_Dio_Ip_PinsChannelType outh;
        Siul2_Dio_Ip_PinsChannelType outl;
        xwid_t porth;
        xwid_t portl;
        xwreg_t cpuirq;
        xwer_t rc;

        if (port < MCALXWDS_GPIO_PORT_NUM) {
                drvdata = soc->dev.data;
                portl = port * (xwid_t)2;
                porth = port * (xwid_t)2 + (xwid_t)1;
                pinmskl = ((Siul2_Dio_Ip_PinsChannelType)pinmask &
                           (Siul2_Dio_Ip_PinsChannelType)0xFFFF);
                pinmskh = ((Siul2_Dio_Ip_PinsChannelType)(pinmask >> (xwsq_t)16) &
                           (Siul2_Dio_Ip_PinsChannelType)0xFFFF);
                outl = ((Siul2_Dio_Ip_PinsChannelType)out &
                        (Siul2_Dio_Ip_PinsChannelType)0xFFFF);
                outh = ((Siul2_Dio_Ip_PinsChannelType)(out >> (xwsq_t)16) &
                        (Siul2_Dio_Ip_PinsChannelType)0xFFFF);
                xwos_splk_lock_cpuirqsv(&drvdata->gpio.lock, &cpuirq);
                Siul2_Dio_Ip_MaskedWritePins(0, portl, outl, pinmskl);
                Siul2_Dio_Ip_MaskedWritePins(0, porth, outh, pinmskh);
                xwos_splk_unlock_cpuirqrs(&drvdata->gpio.lock, cpuirq);
                rc = XWOK;
        } else {
                rc = -EINVAL;
        }
        return rc;
}

static
xwer_t mcalxwds_soc_drv_gpio_input(struct xwds_soc * soc,
                                   xwid_t port, xwsq_t pinmask,
                                   xwsq_t * in)
{
        const struct mcalxwds_soc_cfg * soccfg;
        Siul2_Dio_Ip_GpioType * const * gpio;
        xwsq_t val;

        soccfg = soc->xwccfg;
        gpio = soccfg->gpio.regmap[port];
        val = ((xwsq_t)Siul2_Dio_Ip_ReadPins(gpio[1]) << (xwsq_t)16) & (xwsq_t)0xFFFF0000;
        val |= Siul2_Dio_Ip_ReadPins(gpio[0]);
        *in = pinmask & (xwsq_t)val;
        return XWOK;
}
